1. UI Layer
Browser / SPA where students and staff interact.
Student form Name, course, email → JSON to API.
Picture selector <input type="file"> + client preview.
Presigned PUT Browser uploads directly to S3 using signed URL.
2. API Layer (EC2)
Node.js/Express API with IAM role for S3 & RDS.
CRUD endpoints /v2/students manage metadata in RDS.
API creates picture_key On POST /v2/students, generates students/{id}.jpg and saves to DB.
Presigned URL generator Uses S3 SDK for putObject & getObject.
3. Storage Layer
S3 for binaries, RDS for metadata & relationships.
Amazon S3 (private) Keys like students/{id}.jpg, bucket blocks public access.
Amazon RDS students table stores name, email, course, picture_key.
Read replica Optional replica for heavy reads (e.g., list all students).
End-to-end picture flow – API creates picture_key
Student Picture Flow – Step by Step
UI – Browser
1. Create student (no file yet) POST /v2/students with JSON only.
2. API returns picture_key Response includes picture_key: "students/{id}.jpg".
3. Request upload URL GET /v2/students/:id/picture-upload-url.
4. PUT to S3 Browser uploads JPEG to S3 using presigned URL.
5. View from S3 GET /v2/students/:id/picture-view-url → `
`.
API – EC2
On create Insert student row → get id → build picture_key → update row.
Upload URL Reads picture_key from RDS, generates putObject presigned URL.
View URL Reads picture_key from RDS, generates getObject presigned URL.
Delete picture (optional) DELETE picture API: removes object from S3 + clears picture_key in RDS.
S3 + RDS
RDS students table Stores picture_key as pointer to S3.
S3 key pattern students/{student_id}.jpg or students/{student_id}_{timestamp}.jpg.
Security Private bucket, IAM role with least privilege, short-lived presigned URLs.
REST API Design – CRUD + S3 (API creates picture_key)
On POST /v2/students, the API creates the student row and immediately generates a default picture_key (e.g. students/{id}.jpg) and stores it in RDS.
| Method |
Endpoint |
Purpose |
RDS behaviour |
S3 behaviour |
| POST |
/v2/students |
Create new student (metadata only). |
Insert row → get student_id → generate picture_key = "students/{id}.jpg" → update row. |
None yet (no file uploaded). |
| GET |
/v2/students |
List all students. |
Read from RDS (can use read replica). |
None. |
| GET |
/v2/students/:id |
Get one student. |
Read from RDS. |
None. |
| PUT |
/v2/students/:id |
Update metadata. |
Update RDS row (name, course, email). |
None. |
| DELETE |
/v2/students/:id |
Delete student. |
Delete row from RDS. |
Option: delete object at stored picture_key. |
| GET |
/v2/students/:id/picture-upload-url |
Get presigned URL to upload picture. |
Read existing picture_key from RDS. |
Generate putObject URL for that key. |
| GET |
/v2/students/:id/picture-view-url |
Get presigned URL to view picture. |
Read existing picture_key from RDS. |
Generate getObject URL for that key. |
| DELETE |
/v2/students/:id/picture (optional) |
Delete picture only. |
Set picture_key = NULL in RDS. |
Calls deleteObject on S3. |
Node.js API – create student + picture_key
Presigned upload & view – using stored picture_key
Exam / CA talking points
Why store picture_key in DB? S3 stores the file, but the system needs a stable pointer. RDS holds the object key so the API can generate presigned URLs, delete pictures and join metadata easily.
Why API creates picture_key? Keeps the naming convention centralised, avoids the UI guessing keys. It also allows simple queries such as “find all students who already have picture_key set.”
Security note Bucket stays private, no public URLs. Presigned URLs are time-limited and scoped to a single object. IAM role on EC2 is least-privilege.
MCQ Trainer – CRUD + S3 (API creates picture_key)
S3 Lifecycle Policy
S3 lifecycle rules automate transitions and clean-up for student picture objects.
Transition to Standard-IAAfter 30 days
Transition to Glacier Deep ArchiveAfter 180 days
Expire objectsAfter 365 days (optional)
Glacier & Deep Archive Behaviour
Object moves to Glacier / Deep Archive When an S3 object transitions to GLACIER or DEEP_ARCHIVE, it cannot be read instantly. Presigned GET URLs and getObject() will return InvalidObjectState until the object is restored.
Do we need to change the API code? No. Your API code remains the same. The picture_key stays valid and does not change. Only the object's storage class changes internally in S3.
Why API GET fails for Glacier objects Because Glacier is offline storage — data must be restored before access. The API will receive InvalidObjectState during this time.
Optional: Add restore endpoint Only if your application needs to restore pictures on demand.
Glacier Restore Timing • Expedited: 1–5 minutes (if available)
• Standard: 3–5 hours
• Bulk: 5–12 hours
During this period, presigned GET still fails.
Best practice for student pictures Do NOT transition student profile pictures to Glacier unless they are archived alumni or inactive accounts. Use Standard-IA for cost savings but keep pictures instantly retrievable.